package com.matrix.framework.auth.service.impl;

import com.matrix.framework.auth.data.LoginFailRecordPo;
import com.matrix.framework.auth.data.UserPo;
import com.matrix.framework.auth.repositories.LoginFailRecordRepository;
import com.matrix.framework.auth.repositories.UserRepository;
import com.matrix.framework.auth.service.LoginFailRecordService;
import com.matrix.framework.core.common.enums.auth.UserLock;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

/**
 * 登录失败记录服务实现类
 *
 * Copyright © 海平面工作室 版权所有
 *
 * @Author: Leo
 * @Create: 2024/10/3 11:15
 * @Since 1.0
 */
@Service
public class LoginFailRecordServiceImpl implements LoginFailRecordService {

    private final LoginFailRecordRepository loginFailRecordRepository;
    private final UserRepository userRepository;

    // 锁定配置
    private static final int MAX_FAIL_COUNT = 5; // 最大失败次数
    private static final long LOCK_TIME_WINDOW = 60 * 1000; // 锁定时间窗口（1分钟）

    public LoginFailRecordServiceImpl(LoginFailRecordRepository loginFailRecordRepository, UserRepository userRepository) {
        this.loginFailRecordRepository = loginFailRecordRepository;
        this.userRepository = userRepository;
    }

    @Override
    public Mono<Void> recordLoginFail(String username) {
        long currentTime = System.currentTimeMillis();
        
        return loginFailRecordRepository.findByUsername(username)
                .flatMap(existingRecord -> {
                    // 检查是否在时间窗口内
                    if (currentTime - existingRecord.getFirstFailTime() <= LOCK_TIME_WINDOW) {
                        // 在时间窗口内，增加失败次数
                        existingRecord.setFailCount(existingRecord.getFailCount() + 1);
                        existingRecord.setLastFailTime(currentTime);
                        existingRecord.setUpdateTime(currentTime);
                        
                        return loginFailRecordRepository.save(existingRecord)
                                .then(checkAndLockUser(username, existingRecord.getFailCount()));
                    } else {
                        // 超出时间窗口，重新开始计数
                        return createNewFailRecord(username, currentTime);
                    }
                })
                .switchIfEmpty(createNewFailRecord(username, currentTime));
    }

    @Override
    public Mono<Void> clearLoginFailRecord(String username) {
        return loginFailRecordRepository.deleteByUsername(username);
    }

    @Override
    public Mono<Boolean> shouldLockUser(String username) {
        return loginFailRecordRepository.findByUsername(username)
                .map(record -> {
                    long currentTime = System.currentTimeMillis();
                    // 检查是否在时间窗口内且失败次数达到上限
                    return (currentTime - record.getFirstFailTime() <= LOCK_TIME_WINDOW) 
                            && (record.getFailCount() >= MAX_FAIL_COUNT);
                })
                .defaultIfEmpty(false);
    }

    @Override
    public Mono<Boolean> isUserLocked(String username) {
        return userRepository.findByUsername(username)
                .map(user -> !UserLock.NORMAL.name().equals(user.getStatus()))
                .defaultIfEmpty(false);
    }

    /**
     * 创建新的失败记录
     */
    private Mono<Void> createNewFailRecord(String username, long currentTime) {
        LoginFailRecordPo newRecord = new LoginFailRecordPo();
        newRecord.setUsername(username);
        newRecord.setFailCount(1);
        newRecord.setFirstFailTime(currentTime);
        newRecord.setLastFailTime(currentTime);
        newRecord.setCreateTime(currentTime);
        newRecord.setUpdateTime(currentTime);
        
        return loginFailRecordRepository.save(newRecord)
                .then(checkAndLockUser(username, 1));
    }

    /**
     * 检查并锁定用户
     */
    private Mono<Void> checkAndLockUser(String username, int failCount) {
        if (failCount >= MAX_FAIL_COUNT) {
            return userRepository.findByUsername(username)
                    .flatMap(user -> {
                        user.setStatus(UserLock.PW_LOCK.name());
                        user.setUpdateTime(System.currentTimeMillis());
                        return userRepository.save(user);
                    })
                    .then();
        }
        return Mono.empty();
    }
} 